<?php
// @formatter:off
/**
 * @file Class.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-08-05 00:26:28 +0000 (Mon, 05 Aug 2013) $
 *
 * $Id: Class.php 100 2013-08-05 00:26:28Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/branches/wcomix-1.0/extensions/Repositories/Class.php $
 */

define("WC_E_REPOSITORIES_VERSION",	"1.0");
define("WC_E_REPOSITORIES_ROOTDIR",	dirname(__FILE__));
define("WC_E_REPOSITORIES_URI",		substr(WC_E_REPOSITORIES_ROOTDIR, strlen($_SERVER["DOCUMENT_ROOT"])));
define("WC_E_REPOSITORIES_ALLOWED",	10001);
define("WC_E_REPOSITORIES_MAINDIR",	"/Repository");
define("WC_E_REPOSITORIES_MAX",		100);

$extensionBasicInfo = array(
	"name"		=> "Repositories",
	"description"	=> "Keep comics into repositories when unsorted",
	"author"	=> "Alejandro Dario Simi",
	"version"	=> WC_E_REPOSITORIES_VERSION,
	"url"		=> array(
		"title"		=> "wcomix:Extension:Repositories",
		"href"		=> "http://wiki.daemonraco.com/wiki/Wcomix:Extension:Repositories"
	)
);
$wcCanvasActions["repositories"] = array_merge(array(
	"manager"	=> WC_E_REPOSITORIES_ROOTDIR."/c.repositories.php",
	"template"	=> WC_E_REPOSITORIES_ROOTDIR."/c.repositories.html",
	"name-url"	=> true
), $extensionBasicInfo);

$wcCronScripts["CTRepositories"] = array_merge($extensionBasicInfo, array(
		"auto"		=> true,
		"enabled"	=> true,
		"class"		=> "CTRepositories"
));
$wcCronProfiles[$wcCronProfile][]	= "CTRepositories";

// @formatter:on

$wcPermissions["pyramid-perms"]["ORGANIZER"][] = WC_E_REPOSITORIES_ALLOWED;

class WCERepository {
	/**
	 * @var WCERepository Pointer to the singleton instance.
	 */
	protected static $_Instance = array();
	/**
	 * @var boolean[string] This is a list of flags used to avoid repeating heavy
	 * operations.
	 */
	protected $_flags = array(
		"checkBalance" => false,
		"loadDirectories" => false
	);
	/**
	 * @var WCConnection Pointer to the global connection object.
	 */
	protected $_db = false;
	/**
	 * @var string Shortcut to database tables prefixes.
	 */
	protected $_dbprefix = "";
	/**
	 * @var WCDirectory Main directory pointer.
	 */
	protected $_mainDirectory = false;
	/**
	 * @var WCGContainer This container holds all repositories.
	 */
	protected $_directories = false;
	/**
	 * @var WCDirectory Unbalanced directory pointer.
	 */
	protected $_badDirectory = false;
	/**
	 * @var int Amount of items required to correct an unbalanced directory.
	 */
	protected $_correctionSize = false;
	/**
	 * @var WCGroup Current group.
	 */
	protected $_group = false;
	/**
	 * @var boolean Indicates if main directory exists but it somehow broken.
	 */
	protected $_broken = false;
	/**
	 * @var boolean Indicates if all directories are balanced.
	 */
	protected $_balanced = false;
	/**
	 * @var string This is the directory name to use when all repository's
	 * buckets are full and it's unbalanced.
	 */
	protected $_nextName = false;
	/**
	 * Constructor is protected because it's a singleton.
	 */
	protected function __construct(WCGroup &$group) {
		//
		// Loading global pointers.
		global $wcDatabaseAccess;
		global $wcGroup;
		global $wcDirectoriesHolder;
		//
		// Loading shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess["prefix"];
		$this->_group = $group;
		// @}
		//
		// Creating a list for sub-directories.
		$this->_directories = new WCGContainer($wcDirectoriesHolder, $this->_group);
	}
	/**
	 * Prevent users to clone the instance.
	 */
	public function __clone() {
		trigger_error(__CLASS__.": Clone is not allowed.", E_USER_ERROR);
	}
	//
	// Public methods.
	/**
	 * @return WCDirectory
	 */
	public function badDirectory() {
		return $this->_badDirectory;
	}
	/**
	 * @return boolean
	 */
	public function balanced() {
		$this->checkBalance();
		return $this->_balanced;
	}
	/**
	 * @return WCDirectory
	 */
	public function balancer() {
		$balancerDir = false;

		$this->loadDirectories();

		$directories = $this->directories();
		uasort($directories, "WCDirectory::NameSort");
		foreach($directories as $dir) {
			if($dir->count < WC_E_REPOSITORIES_MAX) {
				$balancerDir = $dir;
				break;
			}
		}

		return $balancerDir;
	}
	/**
	 * @return boolean
	 */
	public function broken() {
		$this->loadDirectories();
		return $this->_broken;
	}
	public function correctionSize() {
		return $this->_correctionSize;
	}
	public function createBalancer() {
		$out = false;

		if(!$this->broken() && !$this->balanced() && !$this->balancer()) {
			global $wcDefaults;
			global $wcDirectoriesHolder;

			$path = WCSanitizer::SystemPath($this->_group->root().$this->_nextName);
			if(!is_dir($path)) {
				//
				// Physically creating the directory.
				mkdir($path, $wcDefaults['directory-permissions'], true);
			}
			$wcDirectoriesHolder->create($this->_nextName, $this->_group);

			$this->reload();

			$out = true;
		}

		return $out;
	}
	/**
	 * @return WCDirectory[]
	 */
	public function directories() {
		$this->loadDirectories();
		return $this->_directories->items();
	}
	/**
	 * @return WCGroup
	 */
	public function group() {
		return $this->_group;
	}
	/**
	 * @return WCDirectory
	 */
	public function mainDirectory() {
		$this->loadDirectories();
		return $this->_mainDirectory;
	}
	public function reload() {
		if(!$this->broken()) {
			$this->_mainDirectory->reload();
			foreach($this->directories() as $dir) {
				$dir->reload();
			}

			foreach($this->_flags as $key => $value) {
				$this->_flags[$key] = false;
			}
		}
	}
	//
	// Protected methods.
	protected function checkBalance() {
		if(!$this->_flags["checkBalance"]) {
			$this->_balanced = true;

			if($this->_mainDirectory->count > 0) {
				$this->_balanced = false;
				$this->_badDirectory = $this->_mainDirectory;
				$this->_correctionSize = 0;
			} else {
				$directories = $this->directories();
				uasort($directories, "WCDirectory::NameSort");

				$prevCount = WC_E_REPOSITORIES_MAX;
				foreach($directories as $dir) {
					$c = $dir->count;
					if($c > WC_E_REPOSITORIES_MAX) {
						$this->_balanced = false;
						$this->_badDirectory = $dir;
						$this->_correctionSize = WC_E_REPOSITORIES_MAX;
						break;
					} elseif($c > $prevCount) {
						$this->_balanced = false;
						$this->_badDirectory = $dir;
						$this->_correctionSize = $prevCount < WC_E_REPOSITORIES_MAX ? 0 : WC_E_REPOSITORIES_MAX;
						break;
					} elseif($c < WC_E_REPOSITORIES_MAX) {
						if($prevCount < WC_E_REPOSITORIES_MAX && $c != 0) {
							$this->_balanced = false;
							$this->_badDirectory = $dir;
							$this->_correctionSize = 0;
							break;
						}
					}

					$prevCount = $dir->count;
				}
			}

			$this->_flags["checkBalance"] = true;
		}
	}
	protected function loadDirectories($first = true) {
		if(!$this->_flags["loadDirectories"]) {
			global $wcDirectoriesHolder;

			$this->_broken = false;
			$this->_flags["loadDirectories"] = true;

			$this->_mainDirectory = $wcDirectoriesHolder->itemByName(WC_E_REPOSITORIES_MAINDIR, $this->_group);
			if($this->_mainDirectory) {
				if($this->_mainDirectory->ok()) {
					$keep = true;
					$i = 1;
					while($keep) {
						$j = $i < 10 ? "0{$i}" : $i;
						$path = WC_E_REPOSITORIES_MAINDIR."/".$j;
						$this->_nextName = $path;
						$srepo = $wcDirectoriesHolder->itemByName($path, $this->_group);
						if($srepo && $srepo->ok()) {
							$this->_directories->addId($srepo->id);
						} else {
							$keep = false;
						}
						$i++;
					}
				} else {
					$this->_broken = true;
					$this->_mainDirectory = false;
				}
			} elseif(!$first) {
				$this->_broken = true;
				$this->_mainDirectory = false;
			} else {
				global $wcDefaults;

				$path = WCSanitizer::SystemPath($this->_group->root().WC_E_REPOSITORIES_MAINDIR);
				if(!is_dir($path)) {
					//
					// Physically creating the directory.
					mkdir($path, $wcDefaults['directory-permissions'], true);
				}
				$wcDirectoriesHolder->create(WC_E_REPOSITORIES_MAINDIR, $this->_group);

				$this->_flags["loadDirectories"] = false;
				$this->_mainDirectory = false;
				$this->loadDirectories(false);
			}
		}
	}
	//
	// Public class methods.
	/**
	 * This class method allows to get the sole instance of this holder.
	 *
	 * @param WCGroup $group Instance filter.
	 * @return WCERepository Returns the singleton instance of this
	 * class.
	 */
	public static function Instance(WCGroup &$group) {
		//
		// Checking if there's no instance yet.
		if(!isset(self::$_Instance[$group->id])) {
			$c = __CLASS__;
			//
			// Creating the sole instance.
			self::$_Instance[$group->id] = new $c($group);
		}
		//
		// Returning the class instance.
		return self::$_Instance[$group->id];
	}
}

//
// Loading cron.
// Warning: they only load when it is a shell execution.
// @{
if(defined("__SHELL__")) {
	class CTRepositories extends WCCronTool {
		//
		// Public methods.
		public function run($spacer = "") {
			global $wcGroupsHolder;

			foreach($wcGroupsHolder->groups() as $group) {
				if(is_readable($group->root)) {
					echo "{$spacer}Checking group '{$group->name}' ({$group->id}):\n";

					$tries = 1;
					$unbalance = true;
					while($unbalance && $tries <= 5) {
						$repo = WCERepository::Instance($group);
						$repo->reload();
						if(!$repo->broken()) {
							if(!$repo->balanced()) {
								echo "{$spacer}\tRepository Status: ".WCColorText::ShellYellow("Unbalanced")." (try: {$tries})\n";
								$unbalance = $this->balance($spacer, $repo);
							} else {
								echo "{$spacer}\tRepository Status: ".WCColorText::ShellGreen("Balanced")."\n";
								$unbalance = false;
							}
						} else {
							echo "{$spacer}\tRepository Status: ".WCColorText::ShellRed("Broken")."\n";
							$unbalance = false;
						}
						$tries++;
					}
				}
			}
		}
		public function showHelp($spacer = "") {
			echo "{$spacer}This tool balance 'WCERepository' directories.\n\n";
			echo "{$spacer}It requires no extra parameters.\n";
		}
		//
		// Protected methods.
		protected function loadOptions() {
			$this->_optionsLoaded = true;
		}
		protected function balance($spacer, WCERepository &$repo) {
			$success = true;
			//
			// Detecting destination directory.
			$toDir = $repo->balancer();
			//
			// Detecting unbalanced directory.
			$fromDir = $repo->badDirectory();

			$requredCount = $repo->correctionSize();

			if($fromDir !== false && $toDir !== false) {
				$offset = $fromDir->count - $requredCount;

				if(($toDir->count + $offset) > WC_E_REPOSITORIES_MAX) {
					$offset = WC_E_REPOSITORIES_MAX - $toDir->count;
				}
				echo "{$spacer}\t\tUnbalanced directory: '{$fromDir->name}' ({$fromDir->id})\n";
				echo "{$spacer}\t\tBalancer directory:   '{$toDir->name}' ({$toDir->id})\n";
				echo "{$spacer}\t\tNomads:               '{$offset}'\n";
				echo "{$spacer}\t\tOffset:               '".($fromDir->count - $requredCount)."'\n";

				$nomads = $this->getNomads($fromDir, $offset);
				foreach($nomads->items() as $comic) {
					echo "{$spacer}\t\tMoving '{$comic->name}' ({$comic->id}) from '{$comic->directory()->name}' ({$comic->directory}) to directory '{$toDir->name}' ({$toDir->id})";
					$futurePath = WCSanitizer::SystemPath("{$toDir->fullPath()}/{$comic->name}");
					if(!file_exists($futurePath)) {
						if($comic->setDirectory($toDir)) {
							echo WCColorText::ShellGreen(" Done")."\n";
						} else {
							echo WCColorText::ShellRed(" Failed")."\n";
						}
					} else {
						echo WCColorText::ShellRed(" Failed")." ('{$futurePath}' already exists)\n";
					}
				}
			} elseif($fromDir === false) {
				echo "{$spacer}\t\t".WCColorText::ShellRed("Unable to get unbalanced directory")."\n";
				$success = false;
			} elseif($toDir === false) {
				if(!$repo->broken() && !$repo->balanced() && !$repo->balancer()) {
					echo "{$spacer}\t\tCreating new balancer directory\n";
					$success = $repo->createBalancer();
				} else {
					echo "{$spacer}\t\t".WCColorText::ShellRed("Unable to get balancer directory")."\n";
					$success = false;
				}
			}

			return $success;
		}
		/**
		 * @param WCDirectory $fromDir
		 * @param int $offset
		 * @return WCGContainer
		 */
		protected function getNomads(WCDirectory &$fromDir, $offset) {
			global $wcComicsHolder;
			$container = new WCGContainer($wcComicsHolder, $fromDir->group());

			$stmt = null;
			$stmtID = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtID)) {
				$stmt = $this->_db->get($stmtID);
			} else {
				$query = "select  cmc_id\n";
				$query .= "from    {$this->_dbprefix}comics\n";
				$query .= "where   cmc_group     = :group\n";
				$query .= " and    cmc_directory = :directory\n";
				$query .= "order by cmc_added asc\n";

				$stmt = $this->_db->prepare($stmtID, $query);
			}

			$params = array(
				":group" => $fromDir->group()->id,
				":directory" => $fromDir->id
			);
			if($stmt->execute($params)) {
				foreach($stmt->fetchAll() as $row) {
					$container->addId($row["cmc_id"]);
					$offset--;
					if(!$offset) {
						break;
					}
				}
			}

			return $container;
		}
	}

}
?>